package pl.com.qapps.datastore.base;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.utils.FutureWrapper;
import com.googlecode.objectify.util.FutureHelper;

import pl.com.qapps.datastore.Store;

/**
 * Base class to help implement {@link Transaction}.
 * 
 * @author Jakub Dykowski
 * 
 */
public abstract class AbstractTransaction implements Transaction {

	protected enum TransactionState {
		BEGUN, COMPLETION_IN_PROGRESS, COMMITTED, ROLLED_BACK, ERROR
	}

	private final TransactionStack txnStack;
	protected TransactionState state = TransactionState.BEGUN;
	protected final ExecutorService dispatcher;

	protected AbstractTransaction(TransactionStack txnStack,
			ExecutorService service) {
		this.txnStack = txnStack;
		this.dispatcher = service;
	}

	/**
	 * Provides the unique identifier for the txn. Blocks on the future since
	 * the handle comes back from the datastore server.
	 */
	protected abstract long getHandle();

	/**
	 * Commits transaction. Possibly run asynchronously.
	 */
	protected abstract void doCommit();

	/**
	 * Rollbacks transaction. Possibly run asynchronously.
	 */
	protected abstract void doRollback();

	@Override
	public void commit() {
		FutureHelper.quietGet(commitAsync());
	}

	public Future<Void> commitAsync() {
		ensureTxnStarted();
		state = TransactionState.COMPLETION_IN_PROGRESS;
		try {
			for (Future<?> f : txnStack.getFutures(this)) {
				FutureHelper.quietGet(f);
			}
			return new FutureWrapper<Void, Void>(
					dispatcher.submit(new Callable<Void>() {

						@Override
						public Void call() throws Exception {
							doCommit();
							return null;
						}

					})) {
				@Override
				protected Void wrap(Void ignore) throws Exception {
					state = TransactionState.COMMITTED;
					return null;
				}

				@Override
				protected Throwable convertException(Throwable cause) {
					state = TransactionState.ERROR;
					return cause;
				}
			};
		} finally {
			txnStack.remove(this);
		}
	}

	@Override
	public void rollback() {
		FutureHelper.quietGet(rollbackAsync());
	}

	public Future<Void> rollbackAsync() {
		ensureTxnStarted();
		state = TransactionState.COMPLETION_IN_PROGRESS;
		try {
			for (Future<?> f : txnStack.getFutures(this)) {
				FutureHelper.quietGet(f);
			}
			return new FutureWrapper<Void, Void>(
					dispatcher.submit(new Callable<Void>() {

						@Override
						public Void call() throws Exception {
							doRollback();
							return null;
						}

					})) {
				@Override
				protected Void wrap(Void ignore) throws Exception {
					state = TransactionState.ROLLED_BACK;
					return null;
				}

				@Override
				protected Throwable convertException(Throwable cause) {
					state = TransactionState.ERROR;
					return cause;
				}
			};
		} finally {
			txnStack.remove(this);
		}
	}

	@Override
	public String getApp() {
		return "unsupported";
	}

	@Override
	public String getId() {
		return Long.toString(getHandle());
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || getClass() != o.getClass()) {
			return false;
		}
		if (!(o instanceof AbstractTransaction)) {
			return false;
		}

		AbstractTransaction that = (AbstractTransaction) o;
		return getHandle() == that.getHandle();
	}

	@Override
	public int hashCode() {
		return (int) (getHandle() ^ (getHandle() >>> 32));
	}

	@Override
	public String toString() {
		return "Txn [" + getHandle() + ", " + state + "]";
	}

	@Override
	public boolean isActive() {
		return state == TransactionState.BEGUN
				|| state == TransactionState.COMPLETION_IN_PROGRESS;
	}

	/**
	 * If {@code txn} is not null and not active, throw
	 * {@link IllegalStateException}.
	 * 
	 * @deprecated now in ApiUtils
	 */
	static void ensureTxnActive(Transaction txn) {
		if (txn != null && !txn.isActive()) {
			throw new IllegalStateException(
					"Transaction with which this operation is "
							+ "associated is not active.");
		}
	}

	private void ensureTxnStarted() {
		if (state != TransactionState.BEGUN) {
			throw new IllegalStateException("Transaction is in state " + state
					+ ".  There is no legal " + "transition out of this state.");
		}
	}
}
